﻿//
// (C) Copyright 2009 Irantha Suwandarathna (iranthas@hotmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EffiProz.Core.Lib;
using EffiProz.Core.DataTypes;
using EffiProz.Core.Indexes;
using EffiProz.Core.Errors;
using EffiProz.Core.Persist;
using EffiProz.Core.Results;
using EffiProz.Core.Navigators;

namespace EffiProz.Core
{
    public class RangeVariable
    {

        public static RangeVariable[] emptyArray = new RangeVariable[] { };

        //
        public Table rangeTable;
        public QNameManager.SimpleName tableAlias;
        private OrderedHashSet columnAliases;
        private QNameManager.SimpleName[] columnAliasNames;
        private OrderedHashSet columnNames;
        public OrderedHashSet namedJoinColumns;
        public HashMap namedJoinColumnExpressions;
        public Index rangeIndex;
        private Object[] emptyData;
        public bool[] columnsInGroupBy;
        public bool hasKeyedColumnInGroupBy;
        public bool[] usedColumns;
        public bool[] updatedColumns;

        // index conditions
        public Expression indexCondition;
        public Expression indexEndCondition;
        public bool isJoinIndex;

        // non-index consitions
        public Expression nonIndexJoinCondition;
        public Expression nonIndexWhereCondition;

        //
        public bool isLeftJoin;              // table joined with LEFT / FULL OUTER JOIN
        public bool isRightJoin;             // table joined with RIGHT / FULL OUTER JOIN
        public bool isMultiFindFirst;        // findFirst() uses multi-column index
        private Expression[] findFirstExpressions;    // expressions for column values
        private int multiColumnCount;
        public int level;

        //
        public int rangePosition;

        // for variable and argument lists
        public HashMappedList variables;

        // variable v.s. argument
        public bool isVariable;

        public RangeVariable(HashMappedList variables, bool isVariable)
        {

            this.variables = variables;
            this.isVariable = isVariable;
            rangeTable = null;
            tableAlias = null;
            emptyData = null;
            columnsInGroupBy = null;
            usedColumns = null;
        }

        public RangeVariable(Table table, QNameManager.SimpleName alias, OrderedHashSet columnList,
                      QNameManager.SimpleName[] columnNameList, ParserDML.CompileContext compileContext)
        {

            rangeTable = table;
            tableAlias = alias;
            columnAliases = columnList;
            columnAliasNames = columnNameList;
            emptyData = rangeTable.getEmptyRowData();
            columnsInGroupBy = rangeTable.getNewColumnCheckList();
            usedColumns = rangeTable.getNewColumnCheckList();
            rangeIndex = rangeTable.getPrimaryIndex();

            compileContext.registerRangeVariable(this);
        }

        public RangeVariable(Table table, int position)
        {

            rangeTable = table;
            tableAlias = null;
            emptyData = rangeTable.getEmptyRowData();
            columnsInGroupBy = rangeTable.getNewColumnCheckList();
            usedColumns = rangeTable.getNewColumnCheckList();
            rangePosition = position;
        }

        public RangeVariable(RangeVariable range)
        {

            rangeTable = range.rangeTable;
            tableAlias = null;
            emptyData = rangeTable.getEmptyRowData();
            columnsInGroupBy = rangeTable.getNewColumnCheckList();
            usedColumns = rangeTable.getNewColumnCheckList();
            rangeIndex = rangeTable.getPrimaryIndex();
            rangePosition = range.rangePosition;
            level = range.level;
        }

        public void setJoinType(bool isLeft, bool isRight)
        {
            isLeftJoin = isLeft;
            isRightJoin = isRight;
        }

        public void addNamedJoinColumns(OrderedHashSet columns)
        {
            namedJoinColumns = columns;
        }

        public void addColumn(int columnIndex)
        {
            usedColumns[columnIndex] = true;
        }

        public void addNamedJoinColumnExpression(String name, Expression e)
        {

            if (namedJoinColumnExpressions == null)
            {
                namedJoinColumnExpressions = new HashMap();
            }

            namedJoinColumnExpressions.put(name, e);
        }

        public ExpressionColumn getColumnExpression(String name)
        {

            return namedJoinColumnExpressions == null ? null
                                                      : (ExpressionColumn)namedJoinColumnExpressions
                                                      .get(name);
        }

        public Table getTable()
        {
            return rangeTable;
        }

        public OrderedHashSet getColumnNames()
        {

            if (columnNames == null)
            {
                columnNames = new OrderedHashSet();

                rangeTable.getColumnNames(this.usedColumns, columnNames);
            }

            return columnNames;
        }

        public OrderedHashSet getUniqueColumnNameSet()
        {

            OrderedHashSet set = new OrderedHashSet();

            if (columnAliases != null)
            {
                set.addAll(columnAliases);

                return set;
            }

            for (int i = 0; i < rangeTable.columnList.size(); i++)
            {
                String name = rangeTable.getColumn(i).getName().name;
                bool added = set.add(name);

                if (!added)
                {
                    throw Error.error(ErrorCode.X_42578, name);
                }
            }

            return set;
        }

        /**
         * Retruns index for column
         *
         * @param columnName name of column
         * @return int index or -1 if not found
         */
        public int findColumn(String columnName)
        {

            if (namedJoinColumnExpressions != null
                    && namedJoinColumnExpressions.containsKey(columnName))
            {
                return -1;
            }

            if (variables != null)
            {
                return variables.getIndex(columnName);
            }
            else if (columnAliases != null)
            {
                return columnAliases.getIndex(columnName);
            }
            else
            {
                return rangeTable.findColumn(columnName);
            }
        }

        public ColumnSchema getColumn(String columnName)
        {

            int index = findColumn(columnName);

            return index < 0 ? null
                             : rangeTable.getColumn(index);
        }

        public ColumnSchema getColumn(int i)
        {

            if (variables != null)
            {
                return (ColumnSchema)variables.get(i);
            }
            else
            {
                return rangeTable.getColumn(i);
            }
        }

        public String getColumnAlias(int i)
        {

            QNameManager.SimpleName name = getColumnAliasName(i);

            return name.name;
        }

        public QNameManager.SimpleName getColumnAliasName(int i)
        {

            if (columnAliases != null)
            {
                return columnAliasNames[i];
            }
            else
            {
                return rangeTable.getColumn(i).getName();
            }
        }

        public bool hasColumnAlias()
        {
            return columnAliases != null;
        }

        public bool resolvesTableName(ExpressionColumn e)
        {

            if (e.tableName == null)
            {
                return true;
            }

            if (e.schema == null)
            {
                if (tableAlias == null)
                {
                    if (e.tableName.Equals(rangeTable.tableName.name))
                    {
                        return true;
                    }
                }
                else if (e.tableName.Equals(tableAlias.name))
                {
                    return true;
                }
            }
            else
            {
                if (e.tableName.Equals(rangeTable.tableName.name)
                        && e.schema.Equals(rangeTable.tableName.schema.name))
                {
                    return true;
                }
            }

            return false;
        }

        public bool resolvesTableName(String name)
        {

            if (name == null)
            {
                return true;
            }

            if (tableAlias == null)
            {
                if (name.Equals(rangeTable.tableName.name))
                {
                    return true;
                }
            }
            else if (name.Equals(tableAlias.name))
            {
                return true;
            }

            return false;
        }

        public bool resolvesSchemaName(String name)
        {

            if (name == null)
            {
                return true;
            }

            if (tableAlias != null)
            {
                return false;
            }

            return name.Equals(rangeTable.tableName.schema.name);
        }

        /**
         * Add all columns to a list of expressions
         */
        public void addTableColumns(EfzArrayList exprList)
        {

            if (namedJoinColumns != null)
            {
                int count = exprList.size();
                int position = 0;

                for (int i = 0; i < count; i++)
                {
                    Expression e = (Expression)exprList.get(i);
                    String columnName = e.getColumnName();

                    if (namedJoinColumns.contains(columnName))
                    {
                        if (position != i)
                        {
                            exprList.remove(i);
                            exprList.add(position, e);
                        }

                        e = getColumnExpression(columnName);

                        exprList.set(position, e);

                        position++;
                    }
                }
            }

            addTableColumns(exprList, exprList.size(), namedJoinColumns);
        }

        /**
         * Add all columns to a list of expressions
         */
        public int addTableColumns(EfzArrayList expList, int position, HashSet exclude)
        {

            Table table = getTable();
            int count = table.getColumnCount();

            for (int i = 0; i < count; i++)
            {
                ColumnSchema column = table.getColumn(i);
                String columnName = columnAliases == null ? column.getName().name
                                                          : (String)columnAliases
                                                              .get(i);

                if (exclude != null && exclude.contains(columnName))
                {
                    continue;
                }

                Expression e = new ExpressionColumn(this, column, i);

                expList.add(position++, e);
            }

            return position;
        }

        public void addTableColumns(Expression expression, HashSet exclude)
        {

            EfzArrayList list = new EfzArrayList();
            Table table = getTable();
            int count = table.getColumnCount();

            for (int i = 0; i < count; i++)
            {
                ColumnSchema column = table.getColumn(i);
                String columnName = columnAliases == null ? column.getName().name
                                                          : (String)columnAliases
                                                              .get(i);

                if (exclude != null && exclude.contains(columnName))
                {
                    continue;
                }

                Expression e = new ExpressionColumn(this, column, i);

                list.add(e);
            }

            Expression[] nodes = new Expression[list.size()];

            list.toArray(nodes);

            expression.nodes = nodes;
        }

        /**
         * Removes reference to Index to avoid possible memory leaks after alter
         * table or drop index
         */
        public void setForCheckConstraint()
        {
            rangeIndex = null;
        }

        /**
         *
         * @param e condition
         * @param index Index object
         * @param isJoin whether a join or not
         */
        public void addIndexCondition(Expression e, Index index, bool isJoin)
        {

            rangeIndex = index;
            isJoinIndex = isJoin;

            switch (e.getType())
            {

                case OpTypes.NOT:
                    indexCondition = e;
                    break;

                case OpTypes.IS_NULL:
                    indexEndCondition = e;
                    break;

                case OpTypes.EQUAL:
                    indexCondition = e;
                    indexEndCondition = indexCondition;
                    break;

                case OpTypes.GREATER:
                case OpTypes.GREATER_EQUAL:
                    indexCondition = e;
                    break;

                case OpTypes.SMALLER:
                case OpTypes.SMALLER_EQUAL:
                    indexEndCondition = e;
                    break;

                default:
                    Error.runtimeError(ErrorCode.U_S0500, "RangeVariable");
                    break;
            }
        }

        /**
         *
         * @param e a join condition
         */
        public void addJoinCondition(Expression e)
        {
            nonIndexJoinCondition =
                ExpressionLogical.andExpressions(nonIndexJoinCondition, e);
        }

        /**
         *
         * @param e a where condition
         */
        public void addWhereCondition(Expression e)
        {
            nonIndexWhereCondition =
                ExpressionLogical.andExpressions(nonIndexWhereCondition, e);
        }

        public void addCondition(Expression e, bool isJoin)
        {

            if (isJoin)
            {
                addJoinCondition(e);
            }
            else
            {
                addWhereCondition(e);
            }
        }

        /**
         * Only multiple EQUAL conditions are used
         *
         * @param exprList list of expressions
         * @param index Index to use
         * @param isJoin whether a join or not
         */
        public void addIndexCondition(Expression[] exprList, Index index, int colCount,
                                bool isJoin)
        {

            rangeIndex = index;
            isJoinIndex = isJoin;

            for (int i = 0; i < colCount; i++)
            {
                Expression e = exprList[i];

                indexEndCondition =
                    ExpressionLogical.andExpressions(indexEndCondition, e);
            }

            if (colCount == 1)
            {
                indexCondition = exprList[0];
            }
            else
            {
                findFirstExpressions = exprList;
                isMultiFindFirst = true;
                multiColumnCount = colCount;
            }
        }

        public bool hasIndexCondition()
        {
            return isMultiFindFirst || indexCondition != null;
        }

        /**
         * Retreives a String representation of this obejct. <p>
         *
         * The returned String describes this object's table, alias
         * access mode, index, join mode, Start, End and And conditions.
         *
         * @return a String representation of this object
         */
        public String describe(Session session)
        {

            StringBuilder sb;
            String temp;
            Index index;
            Index primaryIndex;
            int[] primaryKey;
            bool hidden;
            bool fullScan;

            sb = new StringBuilder();
            index = rangeIndex;
            primaryIndex = rangeTable.getPrimaryIndex();
            primaryKey = rangeTable.getPrimaryKey();
            hidden = false;
            fullScan = (indexCondition == null && indexEndCondition == null);

            if (index == null)
            {
                index = primaryIndex;
            }

            if (index == primaryIndex && primaryKey.Length == 0)
            {
                hidden = true;
                fullScan = true;
            }

            sb.Append(base.ToString()).Append('\n');
            sb.Append("table=[").Append(rangeTable.getName().name).Append("]\n");

            if (tableAlias != null)
            {
                sb.Append("alias=[").Append(tableAlias.name).Append("]\n");
            }

            sb.Append("access=[").Append(fullScan ? "FULL SCAN"
                                                  : "INDEX PRED").Append("]\n");
            sb.Append("index=[");
            sb.Append(index == null ? "NONE"
                                    : index.getName() == null ? "UNNAMED"
                                                              : index.getName()
                                                              .name);
            sb.Append(hidden ? "[HIDDEN]]\n"
                             : "]\n");

            temp = "INNER";

            if (isLeftJoin)
            {
                temp = "LEFT OUTER";

                if (isRightJoin)
                {
                    temp = "FULL";
                }
            }
            else if (isRightJoin)
            {
                temp = "RIGHT OUTER";
            }

            sb.Append("joinType=[").Append(temp).Append("]\n");

            temp = indexCondition == null ? "null"
                                          : indexCondition.describe(session);

            if (findFirstExpressions != null)
            {
                StringBuilder sbt = new StringBuilder();

                for (int i = 0; i < multiColumnCount; i++)
                {
                    sbt.Append(findFirstExpressions[i].describe(session));
                }

                temp = sbt.ToString();
            }

            sb.Append("eStart=[").Append(temp).Append("]\n");

            temp = indexEndCondition == null ? "null"
                                             : indexEndCondition.describe(session);

            sb.Append("eEnd=[").Append(temp).Append("]\n");

            temp = nonIndexJoinCondition == null ? "null"
                                                 : nonIndexJoinCondition.describe(
                                                 session);

            sb.Append("eAnd=[").Append(temp).Append("]");

            return sb.ToString();
        }

        public RangeIteratorMain getIterator(Session session)
        {

            RangeIteratorMain it = new RangeIteratorMain(session, this);

            session.sessionContext.setRangeIterator(it);

            return it;
        }

        public RangeIteratorMain getFullIterator(Session session,
                RangeIteratorMain mainIterator)
        {

            RangeIteratorMain it = new FullRangeIterator(session, this,
                mainIterator);

            session.sessionContext.setRangeIterator(it);

            return it;
        }

        public static RangeIteratorMain getIterator(Session session,
                RangeVariable[] rangeVars)
        {

            if (rangeVars.Length == 1)
            {
                return rangeVars[0].getIterator(session);
            }

            RangeIteratorMain[] iterators =
                new RangeIteratorMain[rangeVars.Length];

            for (int i = 0; i < rangeVars.Length; i++)
            {
                iterators[i] = rangeVars[i].getIterator(session);
            }

            return new JoinedRangeIterator(iterators);
        }

        public class RangeIteratorBase : RangeIterator
        {

            public Session session;
            public int rangePosition;
            public RowIterator it;
            public PersistentStore store;
            public Object[] currentData;
            public Row currentRow;
            public bool _isBeforeFirst;

            public RangeIteratorBase() { }

            public RangeIteratorBase(Session session, PersistentStore store,
                                     TableBase t, int position)
            {

                this.session = session;
                this.rangePosition = position;
                this.store = store;
                it = t.rowIterator(store);
                _isBeforeFirst = true;
            }

            public virtual bool isBeforeFirst()
            {
                return _isBeforeFirst;
            }

            public virtual bool next()
            {

                if (_isBeforeFirst)
                {
                    _isBeforeFirst = false;
                }
                else
                {
                    if (it == null)
                    {
                        return false;
                    }
                }

                currentRow = it.getNextRow();

                if (currentRow == null)
                {
                    return false;
                }
                else
                {
                    currentData = currentRow.getData();

                    return true;
                }
            }

            public Row getCurrentRow()
            {
                return currentRow;
            }

            public Object[] getCurrent()
            {
                return currentData;
            }

            public long getRowid()
            {
                return currentRow == null ? 0
                                          : currentRow.getId();
            }

            public Object getRowidObject()
            {
                return currentRow == null ? null
                                          : (object)(currentRow.getId());
            }

            public virtual void remove() { }

            public virtual void reset()
            {

                if (it != null)
                {
                    it.release();
                }

                it = null;
                currentRow = null;
                _isBeforeFirst = true;
            }

            public virtual int getRangePosition()
            {
                return rangePosition;
            }
        }

        public class RangeIteratorMain : RangeIteratorBase
        {

            public bool hasOuterRow;
            public bool isFullIterator;
            public RangeVariable rangeVar;

            //
            public Table lookupTable;
            public PersistentStore lookupStore;

            public RangeIteratorMain()
                : base()
            {

            }

            public RangeIteratorMain(Session session, RangeVariable rangeVar)
            {

                this.rangePosition = rangeVar.rangePosition;
                this.store = session.sessionData.getRowStore(rangeVar.rangeTable);
                this.session = session;
                this.rangeVar = rangeVar;
                currentData = rangeVar.emptyData;
                _isBeforeFirst = true;

                if (rangeVar.isRightJoin)
                {
                    lookupTable = TableUtil.newLookupTable(session.database);
                    lookupStore = session.sessionData.getRowStore(lookupTable);
                }
            }

            public override bool isBeforeFirst()
            {
                return _isBeforeFirst;
            }

            public override bool next()
            {

                if (_isBeforeFirst)
                {
                    _isBeforeFirst = false;

                    initialiseIterator();
                }
                else
                {
                    if (it == null)
                    {
                        return false;
                    }
                }

                return findNext();
            }

            public override void remove() { }

            public override void reset()
            {

                if (it != null)
                {
                    it.release();
                }

                it = null;
                currentData = rangeVar.emptyData;
                currentRow = null;
                hasOuterRow = false;
                _isBeforeFirst = true;
            }

            public override int getRangePosition()
            {
                return rangeVar.rangePosition;
            }

            /**
             */
            protected virtual void initialiseIterator()
            {

                hasOuterRow = rangeVar.isLeftJoin;

                if (rangeVar.isMultiFindFirst)
                {
                    getFirstRowMulti();

                    if (!rangeVar.isJoinIndex)
                    {
                        hasOuterRow = false;
                    }
                }
                else if (rangeVar.indexCondition == null)
                {
                    if (rangeVar.indexEndCondition == null
                            || rangeVar.indexEndCondition.getType()
                               == OpTypes.IS_NULL)
                    {
                        it = rangeVar.rangeIndex.firstRow(session, store);
                    }
                    else
                    {
                        it = rangeVar.rangeIndex.findFirstRowNotNull(session,
                                store);
                    }
                }
                else
                {

                    // only NOT NULL
                    if (rangeVar.indexCondition.getType() == OpTypes.NOT)
                    {
                        it = rangeVar.rangeIndex.findFirstRowNotNull(session,
                                store);
                    }
                    else
                    {
                        getFirstRow();
                    }

                    if (!rangeVar.isJoinIndex)
                    {
                        hasOuterRow = false;
                    }
                }
            }

            /**
             */
            private void getFirstRow()
            {

                Object value =
                    rangeVar.indexCondition.getRightNode().getValue(session);
                SqlType valueType =
                    rangeVar.indexCondition.getRightNode().getDataType();
                SqlType targetType =
                    rangeVar.indexCondition.getLeftNode().getDataType();
                int exprType = rangeVar.indexCondition.getType();
                int range = 0;

                if (targetType != valueType)
                {
                    range = targetType.compareToTypeRange(value);
                }

                if (range == 0)
                {
                    value = targetType.convertToType(session, value, valueType);
                    it = rangeVar.rangeIndex.findFirstRow(session, store, value,
                                                          exprType);
                }
                else if (range < 0)
                {
                    switch (exprType)
                    {

                        case OpTypes.GREATER_EQUAL:
                        case OpTypes.GREATER:
                            it = rangeVar.rangeIndex.findFirstRowNotNull(session,
                                    store);
                            break;

                        default:
                            it = rangeVar.rangeIndex.emptyIterator();
                            break;
                    }
                }
                else
                {
                    switch (exprType)
                    {

                        case OpTypes.SMALLER_EQUAL:
                        case OpTypes.SMALLER:
                            it = rangeVar.rangeIndex.findFirstRowNotNull(session,
                                    store);
                            break;

                        default:
                            it = rangeVar.rangeIndex.emptyIterator();
                            break;
                    }
                }

                return;
            }

            /**
             * Uses multiple EQUAL expressions
             */
            private void getFirstRowMulti()
            {

                bool convertible = true;
                Object[] currentJoinData =
                    new Object[rangeVar.rangeIndex.getVisibleColumns()];

                for (int i = 0; i < rangeVar.multiColumnCount; i++)
                {
                    SqlType valueType =
                        rangeVar.findFirstExpressions[i].getRightNode()
                            .getDataType();
                    SqlType targetType =
                        rangeVar.findFirstExpressions[i].getLeftNode()
                            .getDataType();
                    Object value =
                        rangeVar.findFirstExpressions[i].getRightNode().getValue(
                            session);

                    if (targetType.compareToTypeRange(value) != 0)
                    {
                        convertible = false;

                        break;
                    }

                    currentJoinData[i] = targetType.convertToType(session, value,
                            valueType);
                }

                it = convertible
                     ? rangeVar.rangeIndex.findFirstRow(session, store,
                         currentJoinData, rangeVar.multiColumnCount)
                     : rangeVar.rangeIndex.emptyIterator();
            }

            /**
             * Advances to the next available value. <p>
             *
             * @return true if a next value is available upon exit
             */
            protected virtual bool findNext()
            {

                bool result = false;

                while (true)
                {
                    currentRow = it.getNextRow();

                    if (currentRow == null)
                    {
                        break;
                    }

                    currentData = currentRow.getData();

                    if (rangeVar.indexEndCondition != null
                            && !rangeVar.indexEndCondition.testCondition(
                                session))
                    {
                        if (!rangeVar.isJoinIndex)
                        {
                            hasOuterRow = false;
                        }

                        break;
                    }

                    if (rangeVar.nonIndexJoinCondition != null
                            && !rangeVar.nonIndexJoinCondition.testCondition(
                                session))
                    {
                        continue;
                    }

                    if (rangeVar.nonIndexWhereCondition != null
                            && !rangeVar.nonIndexWhereCondition.testCondition(
                                session))
                    {
                        hasOuterRow = false;

                        continue;
                    }

                    addFoundRow();

                    result = true;

                    break;
                }

                if (result)
                {
                    hasOuterRow = false;

                    return true;
                }

                it.release();

                currentRow = null;
                currentData = rangeVar.emptyData;

                if (hasOuterRow)
                {
                    result = (rangeVar.nonIndexWhereCondition == null
                              || rangeVar.nonIndexWhereCondition.testCondition(
                                  session));
                }

                hasOuterRow = false;

                return result;
            }

            protected void addFoundRow()
            {

                if (rangeVar.isRightJoin)
                {
                    try
                    {
                        lookupTable.insertData(
                            lookupStore,
                            new Object[] { (currentRow.getPos()) });
                    }
                    catch (CoreException ) { }
                }
            }
        }

        public class FullRangeIterator : RangeIteratorMain
        {

            public FullRangeIterator(Session session, RangeVariable rangeVar,
                                     RangeIteratorMain rangeIterator)
            {

                this.rangePosition = rangeVar.rangePosition;
                this.store = session.sessionData.getRowStore(rangeVar.rangeTable);
                this.session = session;
                this.rangeVar = rangeVar;
                isFullIterator = true;
                _isBeforeFirst = true;
                lookupTable = rangeIterator.lookupTable;
                lookupStore = rangeIterator.lookupStore;
                it = rangeVar.rangeIndex.firstRow(session, store);
            }

            protected override void initialiseIterator() { }

            protected override bool findNext()
            {

                bool result;

                while (true)
                {
                    currentRow = it.getNextRow();

                    if (currentRow == null)
                    {
                        result = false;

                        break;
                    }

                    RowIterator lookupIterator =
                        lookupTable.indexList[0].findFirstRow(session,
                            lookupStore, (currentRow.getPos()),
                            OpTypes.EQUAL);

                    result = !lookupIterator.hasNext();

                    lookupIterator.release();

                    if (result)
                    {
                        currentData = currentRow.getData();

                        if (rangeVar.nonIndexWhereCondition != null
                                && !rangeVar.nonIndexWhereCondition.testCondition(
                                    session))
                        {
                            continue;
                        }

                        _isBeforeFirst = false;

                        return true;
                    }
                }

                it.release();

                currentRow = null;
                currentData = rangeVar.emptyData;

                return result;
            }
        }

        public class JoinedRangeIterator : RangeIteratorMain
        {

            RangeIteratorMain[] rangeIterators;
            int currentIndex = 0;

            public JoinedRangeIterator(RangeIteratorMain[] rangeIterators)
            {
                this.rangeIterators = rangeIterators;
            }

            public override bool isBeforeFirst()
            {
                return _isBeforeFirst;
            }

            public override bool next()
            {

                while (currentIndex >= 0)
                {
                    RangeIteratorMain it = rangeIterators[currentIndex];

                    if (it.next())
                    {
                        if (currentIndex < rangeIterators.Length - 1)
                        {
                            currentIndex++;

                            continue;
                        }

                        currentRow = rangeIterators[currentIndex].currentRow;
                        currentData = currentRow.getData();

                        return true;
                    }
                    else
                    {
                        it.reset();

                        currentIndex--;

                        continue;
                    }
                }

                currentData =
                    rangeIterators[rangeIterators.Length - 1].rangeVar.emptyData;
                currentRow = null;

                for (int i = 0; i < rangeIterators.Length; i++)
                {
                    rangeIterators[i].reset();
                }

                return false;
            }

            public override void reset() { }
        }
    }
}
